"""
升级httprunner 到v3版本
"""
import urllib
from typing import Text, List, Dict
from urllib.parse import urlparse

from FasterRunner.settings import logger
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase, exceptions
from httprunner.models import ProjectMeta
from httprunner.response import uniform_validator
from httprunner.utils import get_platform


class HrunV3(HttpRunner):
    def __init__(self, config, teststeps, debugtalk=None):
        self.config = config
        self.teststeps = teststeps
        if debugtalk:
            project_meta = ProjectMeta()
            project_meta.functions = debugtalk.get("functions", {})
            super().with_project_meta(project_meta)
            super().with_variables(debugtalk.get("variables", {}))


@logger.catch
def hrun_v3_summary_adapt(sumary_list: List[Dict]) -> Dict:
    """ 将hrun_v3的响应结果转换，适应hrun_v1 旧版前端
    :param sumary_list:
    :return:
    """
    suit_summary = {
        "details": [  # 用例集列表
            # {
            #     "base_url":"",
            #     "in_out": {},
            #     "name": "",
            #     "records": [],  # 步骤列表
            #     "stat": "",
            #     "success": "",
            #     "time": ""
            # }  # 用例
        ],
        "platform": get_platform(),
        "stat": {
            "failures": 0,
            "successes": 0,
            "errors": 0,
            "skipped": 0,
            "testsRun": 0,
            "expectedFailures": 0,
            "unexpectedSuccesses": 0
        },
        "success": True,
        "time": {
            "start_at": "",
            "duration": ""
        }
    }
    suit_summary["time"]["start_at"] = sumary_list[0]["time"]["start_at"]
    duration = 0
    for case_sumary in sumary_list:  # 遍历用例报告
        duration += case_sumary["time"]["duration"]
        case_sumary["records"] = case_sumary.pop("step_datas")
        case_sumary["stat"] = {"failures": 0, "successes": 0, "testsRun": 0, "errors": 0, "skipped": 0, "expectedFailures": 0, "unexpectedSuccesses": 0}
        for record in case_sumary["records"]:
            if not record["success"]:
                case_sumary["success"] = False
                suit_summary["success"] = False
            case_sumary["stat"]["testsRun"] += 1
            suit_summary["stat"]["testsRun"] += 1
            record["status"] = record.pop("success")
            if record.get("status"):
                case_sumary["stat"]["successes"] += 1
                suit_summary["stat"]["successes"] += 1
            else:
                case_sumary["stat"]["failures"] += 1
                suit_summary["stat"]["failures"] += 1
            record["attachment"] = ""  # 存储异常信息
            record["meta_data"] = record.pop("data")
            record["meta_data"]["request"] = record["meta_data"]["req_resps"][0]["request"]
            record["meta_data"]["response"] = record["meta_data"]["req_resps"][0]["response"]
            record["meta_data"]["response"]["content"] = record["meta_data"]["response"]["body"]
            record["meta_data"]["request"]["data"] = {}
            body = record["meta_data"]["request"]["body"]
            if body and isinstance(body,str):
                # 将body字符串解析成字典
                blist = str(body).split("&")
                for b in blist:
                    item = b.split("=")
                    record["meta_data"]["request"]["data"][item[0]] = urllib.parse.unquote(item[1])
            if body and isinstance(body,dict):
                record["meta_data"]["request"]["data"]=body
            record["meta_data"]["validators"] = record["meta_data"]["validators"].pop("validate_extractor", [])

        suit_summary["details"].append(case_sumary)
    suit_summary["time"]["duration"] = duration
    return suit_summary


@logger.catch
def merge_dict_from_list(dlist: List[Dict]) -> Dict:
    """ 将列表中的字典合并成一个字典
    :param dlist:
    :return:
    """
    dict_variables = {}
    for item in dlist:
        dict_variables.update(item)
    return dict_variables


@logger.catch
def gen_hrun_v3_args(testcase_list):
    """
    生成适配hrun-v3的参数
    :param testcase_list:
    格式如
    [GET Request]:

    [{
        'config': {
            'name': 'POSTMAN配置',
            'request': {
                'base_url': 'https://postman-echo.com'
            },
            'desc': {},
            'failFast': 'true',
            'variables': [{'foo1': 'config_foo1'}, {'foo2': 'hv_foo2'}],
            'output': [''],
            'refs': {
                'env': {},
                'def-api': {},
                'def-testcase': {},
                'debugtalk': {
                    'variables': {},
                    'functions': {}
                }
            }
        },
        'teststeps': [{
            'name': 'GET Request',
            'times': 1,
            'request': {
                'url': '/get',
                'method': 'GET',
                'verify': False,
                'headers': {'Cookie': 'From=PTQA'},
                'params': {'foo1': '$foo1'}
            },
            'desc': {},
            'extract': [{'result': 'content.args'}],
            'validate': [{'equals': ['status_code', 200]}],
            'variables': [{'foo1': '$foo2'}],
            'setup_hooks': ['${gen_md5("MT")}'],
            'skipIf': False
        }]
    }]


    [POST Forms]:

    [{
        'config': {
            'name': 'POSTMAN配置',
            'request': {
                'base_url': 'https://postman-echo.com'
            },
            'desc': {},
            'failFast': 'true',
            'variables': [{'foo1': 'config_foo1'}, {'foo2': 'hv_foo2'}],
            'output': [''],
            'refs': {
                'env': {},
                'def-api': {},
                'def-testcase': {},
                'debugtalk': {
                    'variables': {},
                    'functions': {}
                }
            }
        },
        'teststeps': [{
            'name': 'POST Forms',
            'times': 1,
            'request': {
                'url': '/post',
                'method': 'POST',
                'verify': False,
                'headers': {'Cookie': 'From=PTQA'},
                'data': {'foo1': '$foo1'}
            },
            'desc': {},
            'extract': [{'result': 'content.args'}],
            'validate': [{'equals': ['status_code', 200]}],
            'variables': [{'foo1': '$foo2'}],
            'teardown_hooks': ['${gen_md5("MT")}'],
            'skipIf': False
        }]
    }]


    [POST Json]:

    [{
        'config': {
            'name': 'POSTMAN配置',
            'request': {
                'base_url': 'https://postman-echo.com'
            },
            'desc': {},
            'failFast': 'true',
            'variables': [{'foo1': 'config_foo1'}, {'foo2': 'hv_foo2'}],
            'output': [''],
            'refs': {
                'env': {},
                'def-api': {},
                'def-testcase': {},
                'debugtalk': {
                    'variables': {},
                    'functions': {}
                }
            }
        },
        'teststeps': [{
            'name': 'POST Json',
            'times': 1,
            'request': {
                'url': '/post',
                'method': 'POST',
                'verify': False,
                'json': {'foo1': '$foo1'}
            },
            'desc': {},
            'variables': [{'foo1': '$foo2'}],
            'skipIf': False
        }]
    }]

    :return:config, teststeps
    """
    logger.debug(f"初始化hrun_v3构造参数：testcase_list={testcase_list}")
    init_list = []
    for testcase in testcase_list:
        # ---------------构造config---------------

        config = testcase.get("config", {})

        # init config name
        config_name = config.get("name", "未定义配置名称")

        # init config variables
        config_variables = merge_dict_from_list(config.get("variables", []))

        # init config base_url
        config_request = config.get("request", {})
        config_base_url = config_request.get("base_url", "")
        test_steps = testcase_list[0].get("teststeps")
        if not config_base_url:
            logger.warning("域名请注意使用配置变量，否则将从第一个step中的url中提取")
            url = test_steps[0].get("request", {}).get("url")
            url_parse = urlparse(url)
            if url_parse.hostname:
                config_base_url = url_parse.scheme + "://" + url_parse.hostname
        # init config_verify
        config_verify = config.get("verify", False)

        # init config_export
        config_export = config.get("export", [])

        # init config_weight
        config_weight = int(config.get("weight", 1))

        # generate httprunner  config
        config_init = (
            Config(config_name)
                .variables(**config_variables)
                .base_url(config_base_url)
                .verify(config_verify)
                .export(*config_export)
                .locust_weight(config_weight),
        )

        # ---------------构造teststeps---------------
        test_steps = testcase.get("teststeps")
        teststeps_init = []
        for teststep in test_steps:
            if teststep.get("skipIf"): continue
            step_name = teststep.get("name")
            if teststep.get("request"):  # generate httprunner teststeps  -- RunRequest
                run_instance = RunRequest(step_name)
            elif teststep.get("testcase"):  # generate httprunner teststeps  -- RunTestCase
                run_instance = RunTestCase(step_name)
            else:
                raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}")

            step_variables = merge_dict_from_list(teststep.get("variables", [{}]))
            run_instance = run_instance.with_variables(**step_variables)

            # todo setup_hooks
            if "setup_hooks" in teststep:
                setup_hooks = teststep["setup_hooks"]
                for hook in setup_hooks:
                    if isinstance(hook, Text):  # hook 为字符串,如 ${gen_md5()}
                        run_instance = run_instance.setup_hook(str(hook))
                    elif isinstance(hook, Dict) and len(hook) == 1:  # hook 为字典,如 {"result":"${gen_md5()"}}
                        assign_var_name, hook_content = list(hook.items())[0]
                        run_instance = run_instance.setup_hook(str(hook), str(assign_var_name))
                    else:
                        raise exceptions.TestCaseFormatError(f"Invalid setup hook: {hook}")

            # init step_request
            # 发送HTTP Request
            if teststep.get("request"):
                request = teststep.get("request")
                method = request["method"].lower().strip()
                url = request["url"]
                # init request method
                if method == "get": run_instance = run_instance.get(url)
                if method == "post": run_instance = run_instance.post(url)
                if method == "put": run_instance = run_instance.put(url)
                if method == "delete": run_instance = run_instance.delete(url)
                if method == "patch": run_instance = run_instance.patch(url)

                if "params" in request:
                    params = request["params"]
                    run_instance = run_instance.with_params(**params)

                if "headers" in request:
                    headers = request["headers"]
                    run_instance = run_instance.with_headers(**headers)

                if "cookies" in request:
                    cookies = request["cookies"]
                    run_instance = run_instance.with_cookies(**cookies)

                if "data" in request:
                    data = request["data"]  # 字典
                    # data 要转格式 "foo1=$foo1&foo2=$foo3" ?
                    if isinstance(data, Text):
                        data = str(data)
                    run_instance = run_instance.with_data(data)

                if "json" in request:
                    req_json = request["json"]
                    if isinstance(req_json, Text):
                        req_json = str(req_json)
                    run_instance = run_instance.with_json(req_json)

                if "timeout" in request:
                    timeout = float(request["timeout"])
                    run_instance = run_instance.set_timeout(timeout)

                if "verify" in request:
                    verify = request["verify"]
                    run_instance = run_instance.set_verify(verify)

                if "allow_redirects" in request:
                    allow_redirects = request["allow_redirects"]
                    run_instance = run_instance.set_allow_redirects(allow_redirects)

                if "upload" in request:
                    upload = request["upload"]
                    run_instance = run_instance.upload(**upload)
            # 调用其他Case
            elif teststep.get("testcase"):
                testcase = teststep["testcase"]
                run_instance = run_instance.call(testcase)

            # todo teardown_hooks
            if "teardown_hooks" in teststep:
                teardown_hooks = teststep["teardown_hooks"]
                for hook in teardown_hooks:
                    if isinstance(hook, Text):
                        run_instance = run_instance.teardown_hook(str(hook))
                    elif isinstance(hook, Dict) and len(hook) == 1:
                        assign_var_name, hook_content = list(hook.items())[0]
                        run_instance = run_instance.teardown_hook(str(hook), str(assign_var_name))
                    else:
                        raise exceptions.TestCaseFormatError(f"Invalid teardown hook: {hook}")

            # todo extract
            if "extract" in teststep:
                # request step
                run_instance = run_instance.extract()
                extracts = merge_dict_from_list(teststep["extract"])
                for extract_name, extract_path in extracts.items():
                    run_instance = run_instance.with_jmespath(extract_path, extract_name)

            # todo export
            if "export" in teststep:
                # reference testcase step
                export: List[Text] = teststep["export"]
                run_instance = run_instance.export(*export)

            # todo validate
            if "validate" in teststep:
                run_instance = run_instance.validate()

                for v in teststep["validate"]:
                    validator = uniform_validator(v)
                    assert_method = validator["assert"]
                    check = validator["check"]
                    if '"' in check:
                        # e.g. body."user-agent" => 'body."user-agent"'
                        check = f"'{check}'"
                    # else:
                    #     check = f'"{check}"'
                    expect = validator["expect"]
                    # if isinstance(expect, Text):
                    #     expect = f'"{expect}"'
                    message = validator["message"]
                    if assert_method == "equal":
                        run_instance = run_instance.assert_equal(check, expect, message)
                    elif assert_method == "not_equal":
                        run_instance = run_instance.assert_not_equal(check, expect, message)
                    elif assert_method == "greater_than":
                        run_instance = run_instance.assert_greater_than(check, expect, message)
                    elif assert_method == "less_than":
                        run_instance = run_instance.assert_less_than(check, expect, message)
                    elif assert_method == "greater_or_equals":
                        run_instance = run_instance.assert_greater_or_equals(check, expect, message)
                    elif assert_method == "less_or_equals":
                        run_instance = run_instance.assert_less_or_equals(check, expect, message)
                    elif assert_method == "length_equal":
                        run_instance = run_instance.assert_length_equal(check, expect, message)
                    elif assert_method == "length_greater_than":
                        run_instance = run_instance.assert_length_greater_than(check, expect, message)
                    elif assert_method == "length_less_than":
                        run_instance = run_instance.assert_length_less_than(check, expect, message)
                    elif assert_method == "length_greater_or_equals":
                        run_instance = run_instance.assert_length_greater_or_equals(check, expect, message)
                    elif assert_method == "length_less_or_equals":
                        run_instance = run_instance.assert_length_less_or_equals(check, expect, message)
                    elif assert_method == "string_equals":
                        run_instance = run_instance.assert_string_equals(check, expect, message)
                    elif assert_method == "startswith":
                        run_instance = run_instance.assert_startswith(check, expect, message)
                    elif assert_method == "endswith":
                        run_instance = run_instance.assert_endswith(check, expect, message)
                    elif assert_method == "regex_match":
                        run_instance = run_instance.assert_regex_match(check, expect, message)
                    elif assert_method == "contains":
                        run_instance = run_instance.assert_contains(check, expect, message)
                    elif assert_method == "contained_by":
                        run_instance = run_instance.assert_contained_by(check, expect, message)
                    elif assert_method == "type_match":
                        run_instance = run_instance.assert_type_match(check, expect, message)
                    else:
                        logger.warning(f"assert_method:{assert_method} 未定义")
            teststeps_init.append(Step(run_instance))

        init_list.append((config_init[0], teststeps_init))
    logger.debug(f"构造hrun_v3参数完成：{init_list}")
    return init_list


if __name__ == "__main__":
    testcase_list = [
        {'config': {'name': '观富昌-测试DEMO配置', 'request': {'base_url': 'https://postman-echo.com'},
                    'desc': {'variables': {'cv_app': '', 'foo1': '', 'foo2': ''}, 'data': {}, 'files': {}, 'params': {}, 'header': {}, 'parameters': {}}, 'failFast': 'true',
                    'variables': [{'cv_app': {'app_id': '57682191', 'app_name': 'demo', 'app_secret': 'd387aa1651a0325fbf037dcbad89ebe5'}}, {'foo1': 'config_bar1'},
                                  {'foo2': 'config_bar2'}], 'output': [''], 'refs': {'env': {}, 'def-api': {}, 'def-testcase': {}, 'debugtalk': {'variables': {
                'ptest02': "{'host': 'conn.thinkinpower.net', 'port': 23306, 'user': 'ptadmin', 'password': 'JrVcU4JlNe', 'use_unicode': True, 'charset': 'utf8', 'cursorclass': <class 'pymysql.cursors.DictCursor'>}",
                'atest02': "{'host': 'conn.thinkinpower.net', 'port': 13306, 'user': 'ptadmin', 'password': 'JrVcU4JlNe', 'use_unicode': True, 'charset': 'utf8', 'cursorclass': <class 'pymysql.cursors.DictCursor'>}",
                'session': '<requests.sessions.Session object at 0x0000000008A6BE10>',
                'run_env': "{'test': {'base_url': 'http://test.p.rfmember.net', 'wallet_app_id': '16845451'}}", 'headers': "{'Connection': 'close'}"},
                'functions': {'timestamp': '',
                              'gen_pt_sign': '',
                              'add_sign': '',
                              'get_app_token': '',
                              'add_app_token': '',
                              'out_trade_no': '',
                              'gen_md5': '',
                              'date_before': '',
                              'add_env_var': '',
                              'get_env_var': '',
                              'get_api_file': '',
                              'get_balance_bills': '',
                              'import_wy_payment_bill': '',

                              'sleep': '',
                              'get_xlsx_by_cols': '',
                              'xlsxPlatform': ''}}}},
         'teststeps': [{'name': 'get with params', 'times': 1,
                        'request': {'url': '/get', 'method': 'GET', 'verify': False, 'headers': {'User-Agent': 'HttpRunner/123'},
                                    'params': {'foo1': '$foo1', 'foo2': '$foo2', 'sum_v': '$sum_v'}},
                        'desc': {'variables': {'foo1': '', 'foo2': '', 'sum_v': ''}, 'data': {}, 'files': {}, 'params': {'foo1': '', 'foo2': '', 'sum_v': ''},
                                 'header': {'User-Agent': ''}, 'extract': {'foo3': ''}}, 'extract': [{'foo3': 'body.args.foo2'}],
                        'validate': [{'equals': ['status_code', 200]}, {'str_eq': ['body.args.foo1', 'bar11']}, {'str_eq': ['body.args.sum_v', '3']},
                                     {'str_eq': ['body.args.foo2', 'bar21']}], 'variables': [{'foo1': 'bar11'}, {'foo2': 'bar21'}, {'sum_v': '123'}],
                        'skipIf': False}]}]
    init_list = gen_hrun_v3_args(testcase_list)
    for init in init_list:
        t = HrunV3(init[0],init[1])
        t.test_start()
        print(t.get_summary().dict())
